﻿namespace Smart.Navigation
{
    using System;
    using System.Collections.Generic;
    using System.Reflection;
    using Smart.Navigation.Context;
    using Smart.Navigation.Parameter;
    using Smart.Navigation.Plugin;

    /// <summary>
    /// 画面制御ナビゲーター
    /// </summary>
    public class Navigator
    {
        //--------------------------------------------------------------------------------//
        // クラス
        //--------------------------------------------------------------------------------//

        #region <クラス>

        /// <summary>
        /// スタック情報
        /// </summary>
        private class StackInfo
        {
            public object Id { get; private set; }

            public object View { get; private set; }

            /// <summary>
            /// コンストラクタ
            /// </summary>
            /// <param name="id">画面ID</param>
            /// <param name="view">ビューインスタンス</param>
            public StackInfo(object id, object view)
            {
                Id = id;
                View = view;
            }
        }

        #endregion

        //--------------------------------------------------------------------------------//
        // イベント
        //--------------------------------------------------------------------------------//

        #region <イベント>

        public event EventHandler<ViewForwardEventArgs> Forwarding;

        public event EventHandler<ViewExitEventArgs> Exited;

        #endregion

        //--------------------------------------------------------------------------------//
        // メンバ
        //--------------------------------------------------------------------------------//

        #region <メンバ>

        private readonly Dictionary<object, Type> idToViewType = new Dictionary<object, Type>();

        private readonly Stack<StackInfo> stackedPages = new Stack<StackInfo>();

        private readonly IViewProvider provider;

        #endregion

        //--------------------------------------------------------------------------------//
        // プロパティ
        //--------------------------------------------------------------------------------//

        #region <プロパティ>

        /// <summary>
        /// プラグインリスト
        /// </summary>
        public ICollection<INavigatorPlugin> Plugins { get; private set; }

        /// <summary>
        /// 現在のビュー
        /// </summary>
        public object CurrentView { get; private set; }

        /// <summary>
        /// 現在のビューID
        /// </summary>
        public object CurrentViewId { get; private set; }

        #endregion

        //--------------------------------------------------------------------------------//
        // メソッド
        //--------------------------------------------------------------------------------//

        #region <メソッド>

        /// <summary>
        /// コンストラクタ
        /// </summary>
        /// <param name="provider">ビュープロバイダ</param>
        public Navigator(IViewProvider provider)
        {
            this.provider = provider;

            Plugins = new List<INavigatorPlugin>
            {
                // デフォルト
                new ParameterPlugin(),
                new ContextPlugin()
            };
        }

        #endregion

        //--------------------------------------------------------------------------------//
        // メソッド.登録
        //--------------------------------------------------------------------------------//

        #region <メソッド.登録>

        /// <summary>
        /// ビュー登録
        /// </summary>
        /// <param name="id">ビューID</param>
        /// <param name="type">ビュー型</param>
        public void AddView(object id, Type type)
        {
            idToViewType.Add(id, type);
        }

        /// <summary>
        /// ビュー自動登録
        /// </summary>
        /// <param name="assembly">探索対象アセンブリ</param>
        public void AutoRegister(Assembly assembly)
        {
            if (assembly == null)
            {
                throw new ArgumentNullException("assembly");
            }

            foreach (var type in assembly.GetTypes())
            {
                foreach (var attr in (ViewAttribute[])Attribute.GetCustomAttributes(type, typeof(ViewAttribute)))
                {
                    AddView(attr.Id, type);
                }
            }
        }

        #endregion

        //--------------------------------------------------------------------------------//
        // メソッド.通知
        //--------------------------------------------------------------------------------//

        #region <メソッド.通知>

        /// <summary>
        /// ビューへの通知
        /// </summary>
        /// <param name="msg">メッセージ</param>
        /// <returns>通知正否</returns>
        public bool Notify(object msg)
        {
            return Notify(msg, null);
        }

        /// <summary>
        /// ビューへの通知
        /// </summary>
        /// <param name="msg">メッセージ</param>
        /// <param name="parameters">パラメータ</param>
        /// <returns>通知正否</returns>
        public bool Notify(object msg, params object[] parameters)
        {
            var support = provider.ResolveEventTarget(CurrentView) as IViewNotifySupport;
            if (support == null)
            {
                return false;
            }

            support.OnViewNotify(this, new ViewNotifyEventArgs(msg, parameters));

            return true;
        }

        #endregion

        //--------------------------------------------------------------------------------//
        // メソッド.遷移
        //--------------------------------------------------------------------------------//

        #region <メソッド.遷移>

        /// <summary>
        /// 画面遷移
        /// </summary>
        /// <param name="id">画面ID</param>
        /// <returns>正否</returns>
        public bool Forward(object id)
        {
            return ForwardInternal(ActionType.Forward, 0, id, null);
        }

        /// <summary>
        /// 画面遷移
        /// </summary>
        /// <param name="id">画面ID</param>
        /// <param name="parameters">パラメータ</param>
        /// <returns>正否</returns>
        public bool Forward(object id, params object[] parameters)
        {
            return ForwardInternal(ActionType.Forward, 0, id, parameters);
        }

        /// <summary>
        /// スタック型画面遷移(プッシュ)
        /// </summary>
        /// <param name="id">画面ID</param>
        /// <returns>正否</returns>
        public bool Push(object id)
        {
            return ForwardInternal(ActionType.Push, 0, id, null);
        }

        /// <summary>
        /// スタック型画面遷移(プッシュ)
        /// </summary>
        /// <param name="id">画面ID</param>
        /// <param name="parameters">パラメータ</param>
        /// <returns>正否</returns>
        public bool Push(object id, params object[] parameters)
        {
            return ForwardInternal(ActionType.Push, 0, id, parameters);
        }

        /// <summary>
        /// スタック型画面遷移(ポップ)
        /// </summary>
        /// <returns>正否</returns>
        public bool Pop()
        {
            return ForwardInternal(ActionType.Pop, 1, null, null);
        }

        /// <summary>
        /// スタック型画面遷移(ポップ)
        /// </summary>
        /// <param name="parameters">パラメータ</param>
        /// <returns>正否</returns>
        public bool Pop(params object[] parameters)
        {
            return ForwardInternal(ActionType.Pop, 1, null, parameters);
        }

        /// <summary>
        /// スタック型画面遷移(ポップ)
        /// </summary>
        /// <param name="level">ポップ数</param>
        /// <returns>正否</returns>
        public bool Pop(int level)
        {
            if (level < 1)
            {
                throw new ArgumentException("level must be greater than 0", "level");
            }

            return ForwardInternal(ActionType.Pop, level, null, null);
        }

        /// <summary>
        /// スタック型画面遷移(ポップ)
        /// </summary>
        /// <param name="level">ポップ数</param>
        /// <param name="parameters">パラメータ</param>
        /// <returns>正否</returns>
        public bool Pop(int level, params object[] parameters)
        {
            if (level < 1)
            {
                throw new ArgumentException("level must be greater than 0", "level");
            }

            return ForwardInternal(ActionType.Pop, level, null, parameters);
        }

        /// <summary>
        /// スタック型画面遷移(ポップ or フォワード)
        /// </summary>
        /// <param name="id">画面ID</param>
        /// <returns>正否</returns>
        public bool PopOrForward(object id)
        {
            return ForwardInternal(ActionType.PopOrForward, 1, id, null);
        }

        /// <summary>
        /// スタック型画面遷移(ポップ or フォワード)
        /// </summary>
        /// <param name="id">画面ID</param>
        /// <param name="parameters">パラメータ</param>
        /// <returns>正否</returns>
        public bool PopOrForward(object id, params object[] parameters)
        {
            return ForwardInternal(ActionType.PopOrForward, 1, id, parameters);
        }

        /// <summary>
        /// スタック型画面遷移(ポップ or フォワード)
        /// </summary>
        /// <param name="level">ポップ数</param>
        /// <param name="id">画面ID</param>
        /// <returns>正否</returns>
        public bool PopOrForward(int level, object id)
        {
            if (level < 1)
            {
                throw new ArgumentException("level must be greater than 0", "level");
            }

            return ForwardInternal(ActionType.PopOrForward, level, id, null);
        }

        /// <summary>
        /// スタック型画面遷移(ポップ or フォワード)
        /// </summary>
        /// <param name="level">ポップ数</param>
        /// <param name="id">画面ID</param>
        /// <param name="parameters">パラメータ</param>
        /// <returns>正否</returns>
        public bool PopOrForward(int level, object id, params object[] parameters)
        {
            if (level < 1)
            {
                throw new ArgumentException("level must be greater than 0", "level");
            }

            return ForwardInternal(ActionType.PopOrForward, level, id, parameters);
        }

        /// <summary>
        /// 画面遷移内部処理
        /// </summary>
        /// <param name="actionType">遷移種別</param>
        /// <param name="level">ポップ数</param>
        /// <param name="id">画面ID</param>
        /// <param name="parameters">パラメータ</param>
        /// <returns>正否</returns>
        private bool ForwardInternal(ActionType actionType, int level, object id, object[] parameters)
        {
            Type type = null;

            if (actionType == ActionType.Pop)
            {
                if (stackedPages.Count < level)
                {
                    return false;
                }
            }

            if (actionType == ActionType.PopOrForward)
            {
                actionType = stackedPages.Count < level ? ActionType.Forward : ActionType.Pop;
            }

            if ((actionType == ActionType.Forward) || (actionType == ActionType.Push))
            {
                if (!idToViewType.ContainsKey(id))
                {
                    return false;
                }

                type = idToViewType[id];
            }

            if (provider.IsAsync)
            {
                provider.BeginInvoke((Action)(() => DoForwardInternal(actionType, level, type, id, parameters)));
            }
            else
            {
                DoForwardInternal(actionType, level, type, id, parameters);
            }

            return true;
        }

        /// <summary>
        /// 画面遷移内部処理
        /// </summary>
        /// <param name="actionType">遷移種別</param>
        /// <param name="level">ポップ数</param>
        /// <param name="type">ビュー型</param>
        /// <param name="id">画面ID</param>
        /// <param name="parameters">パラメータ</param>
        private void DoForwardInternal(ActionType actionType, int level, Type type, object id, object[] parameters)
        {
            try
            {
                DoForwardInternalRaw(actionType, level, type, id, parameters);
            }
            catch (Exception e)
            {
                System.Diagnostics.Debug.WriteLine(e);
                throw;
            }
        }

        /// <summary>
        /// 画面遷移内部処理
        /// </summary>
        /// <param name="actionType">遷移種別</param>
        /// <param name="level">ポップ数</param>
        /// <param name="type">ビュー型</param>
        /// <param name="id">画面ID</param>
        /// <param name="parameters">パラメータ</param>
        private void DoForwardInternalRaw(ActionType actionType, int level, Type type, object id, object[] parameters)
        {
            var previousViewId = CurrentViewId;
            var previousView = CurrentView;
            object newViewId = null;
            object newView = null;

            // 前処理
            if (actionType == ActionType.Forward)
            {
                // 前画面破棄
                ViewClose(previousView);

                // 新規作成
                newView = CreateView(type);
                newViewId = id;
            }
            else if (actionType == ActionType.Push)
            {
                // 前画面破棄
                ViewDeactive(previousView);

                // 新規作成
                newView = CreateView(type);
                newViewId = id;
            }
            else if (actionType == ActionType.Pop)
            {
                // 前画面破棄
                ViewClose(previousView);

                // スタック破棄
                for (var i = 0; i < level - 1; i++)
                {
                    var closeInfo = stackedPages.Pop();
                    ViewClose(closeInfo.View);
                    ViewDispose(closeInfo.View);
                }

                // スタック復帰
                var info = stackedPages.Pop();

                // 画面
                newView = info.View;
                newViewId = info.Id;
            }

            // プラグインコンテキスト
            var context = new PluginContext(actionType);

            // プラグイン実行
            PluginOnFowardFrom(context, previousView);

            // カレント更新
            CurrentViewId = newViewId;
            CurrentView = newView;

            // プラグイン実行
            PluginOnForwadTo(context, CurrentView);

            // パラメータ作成
            var args = new ViewForwardEventArgs(CurrentViewId, previousViewId, previousView, parameters, actionType == ActionType.Pop);

            // ナビゲーターイベント
            if (Forwarding != null)
            {
                Forwarding(this, args);
            }

            // 画面表示
            ViewActive(CurrentView, args);

            // 後処理
            if ((actionType == ActionType.Forward) ||
                (actionType == ActionType.Pop))
            {
                // 前画面破棄
                ViewDispose(previousView);
            }
            else if (actionType == ActionType.Push)
            {
                // スタック保存
                stackedPages.Push(new StackInfo(previousViewId, previousView));
            }
        }

        /// <summary>
        /// ナビゲーター終了処理
        /// </summary>
        public void Exit()
        {
            // 画面破棄
            if (CurrentView != null)
            {
                ViewClose(CurrentView);
                ViewDispose(CurrentView);
                CurrentView = null;
            }

            // スタック破棄
            while (stackedPages.Count > 0)
            {
                var info = stackedPages.Pop();
                ViewClose(info.View);
                ViewDispose(info.View);
            }

            // ナビゲーター後処理
            if (Exited != null)
            {
                Exited(this, new ViewExitEventArgs());
            }

            CurrentView = null;
            CurrentViewId = null;
        }

        #endregion

        //--------------------------------------------------------------------------------//
        // メソッド.ヘルパー
        //--------------------------------------------------------------------------------//

        #region <メソッド.ヘルパー>

        /// <summary>
        /// 遷移元ビューイベント
        /// </summary>
        /// <param name="context">プラグインコンテキスト</param>
        /// <param name="view">遷移元ビュー</param>
        private void PluginOnFowardFrom(PluginContext context, object view)
        {
            if (view != null)
            {
                var target = provider.ResolveEventTarget(view);
                foreach (var plugin in Plugins)
                {
                    plugin.OnForwardFrom(context, target);
                }
            }
        }

        /// <summary>
        /// 遷移先ビューイベント
        /// </summary>
        /// <param name="context">プラグインコンテキスト</param>
        /// <param name="view">遷移先ビュー</param>
        private void PluginOnForwadTo(PluginContext context, object view)
        {
            var target = provider.ResolveEventTarget(view);
            foreach (var plugin in Plugins)
            {
                plugin.OnForwardTo(context, target);
            }
        }

        /// <summary>
        /// ビュー作成
        /// </summary>
        /// <param name="type">型</param>
        /// <returns>ビューインスタンス</returns>
        private object CreateView(Type type)
        {
            // ビュー作成
            var view = provider.ViewCreate(type);

            var aware = provider.ResolveEventTarget(view) as INavigatorAware;
            if (aware != null)
            {
                aware.SetNavigator(this);
            }

            return view;
        }

        /// <summary>
        /// ビュー活性化
        /// </summary>
        /// <param name="view">ビューインスタンス</param>
        /// <param name="args">引数</param>
        private void ViewActive(object view, ViewForwardEventArgs args)
        {
            var support = provider.ResolveEventTarget(view) as IViewEventSupport;

            // 初期化イベント
            if (support != null)
            {
                support.OnViewOpen(args);
            }

            // 活性化
            provider.ViewActive(view);

            // 活性化イベント
            if (support != null)
            {
                support.OnViewActived(args);
            }
        }

        /// <summary>
        /// ビュー非活性化
        /// </summary>
        /// <param name="view">ビューインスタンス</param>
        private void ViewDeactive(object view)
        {
            if (view == null)
            {
                return;
            }

            // 非活性化
            provider.ViewDeactive(view);
        }

        /// <summary>
        /// ビュークローズ
        /// </summary>
        /// <param name="view">ビューインスタンス</param>
        private void ViewClose(object view)
        {
            if (view == null)
            {
                return;
            }

            var support = provider.ResolveEventTarget(view) as IViewEventSupport;

            // クローズイベント
            if (support != null)
            {
                support.OnViewClose();
            }
        }

        /// <summary>
        /// ビュー破棄
        /// </summary>
        /// <param name="view">ビューインスタンス</param>
        private void ViewDispose(object view)
        {
            if (view == null)
            {
                return;
            }

            // 解除
            provider.ViewDispose(view);
        }

        #endregion
    }
}